﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;
using System.Xml;
using CIAWorldMap;

namespace OSMLib
{
    public static class OsmCache
    {
        static Dictionary<string, string> _blacklist;
        static readonly Dictionary<string, MapSegment> _normalSegments;
        static readonly LaosBorder _laosBorder;

        const string CACHE_DIRECTORY = @"f:\osm";
        const string CACHE_BLACK_LIST_FILENAME = CACHE_DIRECTORY +  @"\blacklist.txt";

        static OsmCache()
        {
            _laosBorder = new LaosBorder();
            _normalSegments = new Dictionary<string, MapSegment>();

            ArchiveLoader.ReadBoundaries(_normalSegments, _laosBorder);
        }

        private static void FromFile1(string szFileName, Dictionary<int, IOsmNode> nodes)
        {
            if (_blacklist.ContainsKey(szFileName))
                return;

            // trivially remove files that don't have data
            var fi = new FileInfo(szFileName);
            if (fi.Length <= 184)
            {
                AddBlackList(szFileName);
                return;
            }

            string contents;
            try
            {
                using (var s = new StreamReader(szFileName))
                {
                    contents = s.ReadToEnd();
                    s.Close();
                }
            }
            catch (IOException)
            {
                return;
            }

            bool datasetUpdated = false;
            FromString1(contents, ref datasetUpdated, nodes);
            if (!datasetUpdated)
            {
                AddBlackList(szFileName);
            }
        }

        private static void ReadBlackList()
        {
            _blacklist = new Dictionary<string, string>();
            
            if (!File.Exists(CACHE_BLACK_LIST_FILENAME))
                return;

            using (TextReader tr = new StreamReader(CACHE_BLACK_LIST_FILENAME))
            {
                while (true)
                {
                    string line = tr.ReadLine();

                    if (line == null)
                        return;
                    _blacklist.Add(line, "");
                }
            }
        }

        private static void AddBlackList(string szFileName)
        {
            try
            {
                using (var tw = new StreamWriter(CACHE_BLACK_LIST_FILENAME, true))
                {
                    tw.Write(szFileName + "\r\n");
                    tw.Flush();
                    tw.Close();
                }
            }
            catch (Exception ex)
            {
                // don't worry too much if we failed to add the file name to the blacklist - probably Windows was indexing the blacklist file...
                Debug.WriteLine(ex.Message);
            }
        }

        public static void FromURL(double lat, double lon)
        {
            FromURL1(lat, lon);
            FromURL1(lat, lon); // just in case we failed...
        }

        private static void FromURL1(double lat, double lon)
        {
            if (!_laosBorder.PointInPolygon(new Coord(lat, lon)) &&
                !_laosBorder.PointInPolygon(new Coord(lat, lon + 0.02)) &&
                !_laosBorder.PointInPolygon(new Coord(lat + 0.02, lon + 0.02)) &&
                    !_laosBorder.PointInPolygon(new Coord(lat + 0.02, lon)))
            {
                return;
            }

            //GET /api/0.5/map?bbox=left,bottom,right,top
            //0.25 degrees of latitude or longitude

            //string url = @"http://api.openstreetmap.org/api/0.5/map?bbox=-0.76,52.04,-0.74,52.06";

            string left = lon.ToString("0.00");
            string bottom = lat.ToString("0.00");
            string right = (lon + 0.02).ToString("0.00");
            string top = (lat + 0.02).ToString("0.00");

            if (!Directory.Exists(CACHE_DIRECTORY + @"\" + left))
            {
                Directory.CreateDirectory(CACHE_DIRECTORY + @"\" + left);
            }

            string szFile = CACHE_DIRECTORY + @"\" + left + "\\" + left + bottom + ".XML";

            if (File.Exists(szFile))
            {
                return;
            }
        
            string url = @"http://api.openstreetmap.org/api/0.6/map?bbox=" + left + "," + bottom + "," + right + "," + top;
            
            // prepare the web page we will be asking for
            GetOsmFileFromWebServer(szFile, url);
        }

        public static void AdHoc(string filename, double leftLon, double bottomLat, double rightLon, double toplat)
        {
            if (leftLon >= rightLon)
                throw new ApplicationException("leftLon >= rightLon");

            if (bottomLat >= toplat)
                throw new ApplicationException("bottomLat >= toplat");


            //GET /api/0.5/map?bbox=left,bottom,right,top
            //0.25 degrees of latitude or longitude

            //string url = @"http://api.openstreetmap.org/api/0.5/map?bbox=-0.76,52.04,-0.74,52.06";

            string left = leftLon.ToString("0.00");
            string bottom = bottomLat.ToString("0.00");
            string right = rightLon.ToString("0.00");
            string top = toplat.ToString("0.00");

            if (!Directory.Exists(CACHE_DIRECTORY + @"\adhoc"))
            {
                Directory.CreateDirectory(CACHE_DIRECTORY + @"\" + left);
            }

            string szFile = CACHE_DIRECTORY + @"\adhoc\" + filename;

            if (File.Exists(szFile))
            {
                return;
            }

            string url = @"http://api.openstreetmap.org/api/0.6/map?bbox=" + left + "," + bottom + "," + right + "," + top;

            // prepare the web page we will be asking for
            GetOsmFileFromWebServer(szFile, url);
        }


        private static void GetOsmFileFromWebServer(string szFile, string url)
        {
            var request = (HttpWebRequest)WebRequest.Create(url);
            request.Timeout = 5000; // had to extend to 50 seconds for one tile in section 8 - had a load of references.

            HttpWebResponse response;
            try
            {
                // execute the request
                response = (HttpWebResponse)request.GetResponse();
            }
            catch (WebException ex)
            {
                Debug.WriteLine(ex);
                if (ex.Message.Contains("(509)"))
                {
                    Thread.Sleep(60 * 1000 * 5);
                }
                return;
            }

            try
            {
                Stream resStream = response.GetResponseStream();
          
                string xml = FromStream1(resStream);

                using (var tw = new StreamWriter(szFile))
                {
                    tw.Write(xml);
                    tw.Flush();
                    tw.Close();
                }
                Debug.WriteLine(szFile);
                Thread.Sleep(500); // ease things!
            }
            catch (WebException)
            {
            }
        }

        /// <summary>
        /// Load the osm mapping data from the file store. 
        /// </summary>
    
        public static void FromCache()
        {
            ReadBlackList();

            var nodes = new Dictionary<int, IOsmNode>();

            string[] root = Directory.GetDirectories("f:\\osm");

            foreach (string dir in root)
            {
                string[] files = Directory.GetFiles(dir);
                foreach (string f in files)
                {
                    FromFile1(f, nodes);
                }
            }
            BuildBoundingBoxes();

            Osm.NamedNodes.Clear();
            foreach(OsmNode node in nodes.Values)
            {
                if (node._hasName)
                {
                    Osm.NamedNodes.Add(node);
                }
            }
        }

        private static void BuildBoundingBoxes()
        {
            foreach (OsmWay n in Osm.Ways.Values)
            {
                n.BuildBoundingBox();
            }
        }

        private static string FromStream1(Stream resStream)
        {
            var reader = new StreamReader(resStream);
            return reader.ReadToEnd();
        }

        static readonly XmlParserContext _context = new XmlParserContext(null, null, null, XmlSpace.None);

        private static void FromString1(string contents, ref bool datasetUpdated, Dictionary<int, IOsmNode> nodes)
        {
            var textReader = new XmlTextReader(contents, XmlNodeType.Element, _context);

            textReader.Read();

            IOsmNode currentNode = null;
            IOsmWay currentWay = null;
            bool parseWay = false;

            while (textReader.Read())
            {
                if (textReader.IsStartElement())
                {
                    if (textReader.Name == "node")
                    {
                        parseWay = false;

                        string id = textReader.GetAttribute("id");
                   
                        if (id != null)
                        {
                            int nodeID = int.Parse(id);

                            string lat = textReader.GetAttribute("lat");
                            string lon = textReader.GetAttribute("lon");

                            if (!nodes.TryGetValue(nodeID, out currentNode))
                            {
                                currentNode = new OsmNode(lat, lon);
                                nodes.Add(nodeID, currentNode);
                                datasetUpdated = true;
                            }
                        }
                    }

                    if (textReader.Name == "tag")
                    {
                        if ((parseWay)&&(currentWay != null))
                        {
                            string refe = textReader.GetAttribute("ref");
                            if (refe != null)
                            {
                                int nodeReference = int.Parse(refe);
                                currentWay.AddNode(nodes[nodeReference]);
                            }
                            else
                            {
                                currentWay.AddTag(textReader.GetAttribute("k"), textReader.GetAttribute("v"));
                            }
                        }
                        else
                        {
                            if (currentNode != null)
                            {
                                currentNode.AddTag(textReader.GetAttribute("k"), textReader.GetAttribute("v"),
                                                   ref datasetUpdated);
                            }
                        }
                        textReader.Read();
                    }

                    if ((textReader.Name == "nd")&& (currentWay != null))
                    {
                        if (parseWay)
                        {
                            string refe = textReader.GetAttribute("ref");
                            if (refe != null)
                            {
                                int nodeReference = int.Parse(refe);
                                currentWay.AddNode(nodes[nodeReference]);
                                datasetUpdated = true;
                            }
                        }
                        textReader.Read();
                    }

                    if (textReader.Name == "way")
                    {
                        parseWay = true;
                        string id = textReader.GetAttribute("id");
                        if (id != null)
                        {
                            int wayID = int.Parse(id);
                            currentWay = new OsmWay(wayID);

                            if (!Osm.Ways.ContainsKey(wayID))
                            {
                                Osm.Ways.Add(wayID, currentWay);
                                datasetUpdated = true;
                            }
                        }
                    }
                }
            }
        }
    }
}

